Storing and Retrieving Data Using Cloud Firestore
Course: React Native Training
Topic: Storing and Retrieving Data Using Cloud Firestore
Course Link: React Native Training at JustAcademy
Demo Registration: Register for a Course Demo
Official Website: JustAcademy
1. Introduction to Cloud Firestore
Cloud Firestore is a flexible, scalable NoSQL cloud database provided by Firebase. It allows developers to store, retrieve, update, and delete application data. Firestore supports structured documents, flexible queries, real-time listeners, and offline capabilities in supported client environments.
In React Native applications, Cloud Firestore can be used to build task managers, shopping applications, messaging systems, user profiles, learning platforms, booking applications, and many other data-driven mobile experiences.
For example, a task management application can store each task's title, description, completion status, owner, and creation time in Firestore. The React Native application can retrieve the tasks and display them in a list.
1.1 What Does Storing Data Mean?
Storing data means saving application information so that it can be retrieved later. In Firestore, data is stored as documents containing fields. Documents are organized into collections.
For example, a student document might contain a name, email address, course name, and enrollment status.
1.2 What Does Retrieving Data Mean?
Retrieving data means reading information previously stored in a database. An application can retrieve a single document, multiple documents from a collection, or documents matching specified query conditions.
For example, a React Native application can retrieve all tasks assigned to a particular user or retrieve only tasks whose completion status is false.
1.3 Why Use Cloud Firestore?
- Store application data in the cloud.
- Organize records into collections and documents.
- Retrieve individual records using document identifiers.
- Filter and sort data using supported queries.
- Listen for changes and update the interface in real time.
- Support offline workflows where the SDK and platform configuration allow them.
- Control access using Firebase Authentication and Firestore Security Rules.
- Scale data-driven applications without managing database servers directly.
2. Learning Objectives
After completing this topic, learners should be able to:
- Explain the Firestore data model.
- Understand collections, documents, fields, and document IDs.
- Configure Firestore in a React Native or Expo application.
- Insert new documents into a collection.
- Create documents with custom IDs.
- Retrieve a single document and multiple documents.
- Filter, sort, and limit query results.
- Update and delete stored data.
- Listen for real-time database changes.
- Handle loading states, errors, and empty results.
- Understand Firestore security rules and access control.
- Build a practical Firestore-powered React Native project.
3. Understanding the Firestore Data Model
Firestore is a document-oriented NoSQL database. Instead of organizing data primarily into relational tables and rows, it organizes information into collections and documents.
3.1 What Is a Collection?
A collection is a group of documents. Collections help organize related records into a logical structure.
Examples of collection names include:
users
products
tasks
orders
courses
3.2 What Is a Document?
A document is an individual record stored in a collection. Every document has an identifier and contains fields that hold its data.
Example document in a students collection:
{
"name": "Aarav Sharma",
"email": "[email protected]",
"course": "React Native",
"active": true,
"age": 22
}
The example document contains a student's name, email, course, account status, and age.
3.3 What Is a Field?
A field is an individual property inside a document. A field has a name and a value.
| Field |
Example Value |
Data Type |
name |
"Aarav Sharma" |
String |
age |
22 |
Number |
active |
true |
Boolean |
skills |
["React", "JavaScript"] |
Array |
address |
{ "city": "Mumbai" } |
Map/Object |
createdAt |
Firestore timestamp |
Timestamp |
3.4 What Is a Document ID?
A document ID uniquely identifies a document within its collection. Firestore supports automatically generated document IDs and developer-defined IDs.
Example path:
students/student_001
In this path, students is the collection and student_001 is the document ID.
3.5 Example Collection Structure
Firestore Database
|
|-- students (collection)
| |-- student_001 (document)
| | |-- name: "Aarav Sharma"
| | |-- course: "React Native"
| | |-- active: true
| |
| |-- student_002 (document)
| |-- name: "Mira Patel"
| |-- course: "JavaScript"
| |-- active: true
|
|-- tasks (collection)
| |-- task_001 (document)
| |-- title: "Learn Firestore"
| |-- completed: false
4. Supported Firestore Data Types
Firestore supports several data types that can represent application information.
| Data Type |
Description |
Example |
| String |
Text values. |
"React Native" |
| Number |
Numeric values, including integers and floating-point values. |
25, 89.5 |
| Boolean |
True or false values. |
true |
| Array |
An ordered collection of values. |
["HTML", "CSS"] |
| Map |
Nested fields grouped together. |
{ "city": "Mumbai" } |
| Timestamp |
Represents a point in time. |
Server-generated creation time |
| Null |
Represents an explicitly empty value. |
null |
| GeoPoint |
Represents geographic coordinates. |
Latitude and longitude |
| Document Reference |
References another document. |
Reference to a user record |
Choose data types consistently. For example, store dates as timestamps when date-based sorting and filtering are required, rather than mixing formatted strings and timestamp values.
5. Setting Up Cloud Firestore
Before writing or reading data, create a Firebase project, register your application, and configure the appropriate Firebase SDK.
5.1 Create a Firebase Project
- Open the Firebase Console.
- Sign in with your Google account.
- Create a Firebase project or select an existing project.
- Register the application according to your SDK setup.
- Open the Firestore Database section.
- Create a Firestore database by following the console instructions.
- Configure appropriate security rules for your application.
5.2 Install the Firebase JavaScript SDK
For an Expo application using the Firebase JavaScript SDK, run the following command from the project directory:
npx expo install firebase
For a compatible React Native project not using Expo, install the package using your package manager and verify the SDK's React Native requirements.
5.3 Create the Firebase Initialization File
Create a file named src/services/firebase.ts. The following example initializes the Firebase app for the JavaScript SDK.
import { initializeApp, getApp, getApps } from 'firebase/app';
const firebaseConfig = {
apiKey: 'YOUR_FIREBASE_API_KEY',
authDomain: 'YOUR_PROJECT_ID.firebaseapp.com',
projectId: 'YOUR_PROJECT_ID',
storageBucket: 'YOUR_STORAGE_BUCKET',
messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
appId: 'YOUR_FIREBASE_APP_ID',
};
const app = getApps().length
? getApp()
: initializeApp(firebaseConfig);
export default app;
Replace the placeholder values with the configuration values from your Firebase project. Keep initialization centralized so the application can reuse the same Firebase app instance.
5.4 Initialize Cloud Firestore
Create a file named src/services/firestore.ts.
import { getFirestore } from 'firebase/firestore';
import app from './firebase';
const db = getFirestore(app);
export default db;
The getFirestore() function returns a Firestore instance associated with the initialized Firebase application. Other service files can import this instance when they need to read or write data.
6. Understanding Firestore CRUD Operations
CRUD stands for Create, Read, Update, and Delete. These operations represent the basic ways an application manages stored information.
| Operation |
Purpose |
Common Firestore Functions |
| Create |
Add a new document. |
addDoc(), setDoc() |
| Read |
Retrieve documents. |
getDoc(), getDocs(), onSnapshot() |
| Update |
Modify existing document fields. |
updateDoc(), setDoc() |
| Delete |
Remove a document. |
deleteDoc() |
CRUD operations form the foundation of most Firestore-powered mobile applications.
7. Storing Data Using addDoc()
The addDoc() function creates a new document inside a collection and automatically generates a document ID. This is useful when the application does not need to specify the ID itself.
7.1 Import Required Functions
import {
addDoc,
collection,
serverTimestamp,
} from 'firebase/firestore';
import db from './services/firestore';
7.2 Create a New Document
export async function addStudent() {
try {
const studentData = {
name: 'Aarav Sharma',
email: '[email protected]',
course: 'React Native',
active: true,
createdAt: serverTimestamp(),
};
const documentRef = await addDoc(
collection(db, 'students'),
studentData
);
console.log('Student ID:', documentRef.id);
return documentRef.id;
} catch (error) {
console.error('Error adding student:', error);
throw error;
}
}
7.3 Explanation
collection(db, 'students') references the students collection.
studentData contains the fields to store.
serverTimestamp() requests a timestamp based on the server's time.
addDoc() creates the document with an automatically generated ID.
documentRef.id provides the new document's ID after a successful write.
try...catch handles errors from the asynchronous operation.
7.4 When Should You Use addDoc()?
Use addDoc() when every new record can receive a unique generated ID, such as a new support ticket, task, feedback entry, or order record.
8. Storing Data Using setDoc()
The setDoc() function writes data to a specific document path. It is useful when the application needs to choose the document ID or when a stable ID is already available.
8.1 Example with a Custom Document ID
import {
doc,
setDoc,
serverTimestamp,
} from 'firebase/firestore';
import db from './services/firestore';
export async function saveStudentProfile() {
try {
await setDoc(doc(db, 'students', 'student_001'), {
name: 'Mira Patel',
course: 'JavaScript',
active: true,
updatedAt: serverTimestamp(),
});
console.log('Student profile saved.');
} catch (error) {
console.error('Error saving student profile:', error);
throw error;
}
}
8.2 setDoc() vs addDoc()
| Feature |
addDoc() |
setDoc() |
| Document ID |
Generated automatically. |
Specified by the application. |
| Typical use |
New records with generated IDs. |
Known or predictable document IDs. |
| Overwriting |
Creates a new document with a generated ID. |
Writes to the specified path and can replace existing document fields if merge is not enabled. |
| Merge behavior |
Not applicable to its standard create operation. |
Can merge supplied fields with existing fields using supported options. |
Important: Calling setDoc() without merge options can replace the document's existing fields. Use it carefully when updating existing records.
8.3 Using setDoc() with Merge
import { doc, setDoc } from 'firebase/firestore';
import db from './services/firestore';
await setDoc(
doc(db, 'students', 'student_001'),
{
course: 'Advanced React Native',
},
{ merge: true }
);
This example updates the supplied course field while preserving other existing fields. If the document does not exist, Firestore creates it with the supplied data.
9. Retrieving a Single Document Using getDoc()
The getDoc() function retrieves a specific document using its collection path and document ID.
9.1 Example
import { doc, getDoc } from 'firebase/firestore';
import db from './services/firestore';
export async function getStudent(studentId: string) {
try {
const documentRef = doc(db, 'students', studentId);
const documentSnapshot = await getDoc(documentRef);
if (documentSnapshot.exists()) {
return {
id: documentSnapshot.id,
...documentSnapshot.data(),
};
}
return null;
} catch (error) {
console.error('Error retrieving student:', error);
throw error;
}
}
9.2 Explanation
doc() creates a reference to a particular document.
getDoc() requests the document data.
exists() checks whether the document exists in the returned snapshot.
data() retrieves the document's field values.
- The document ID can be included in the returned object for use in lists and navigation.
9.3 Example Usage
async function showStudent() {
const student = await getStudent('student_001');
if (student) {
console.log('Student:', student);
} else {
console.log('Student not found.');
}
}
Use this approach when the application already knows which document it needs, such as a profile page or a details screen.
10. Retrieving Multiple Documents Using getDocs()
The getDocs() function retrieves the results of a collection reference or query. It is useful for loading a list of records, such as products, tasks, or students.
10.1 Retrieve All Documents from a Collection
import {
collection,
getDocs,
} from 'firebase/firestore';
import db from './services/firestore';
export async function getAllStudents() {
try {
const querySnapshot = await getDocs(
collection(db, 'students')
);
const students = querySnapshot.docs.map((documentSnapshot) => ({
id: documentSnapshot.id,
...documentSnapshot.data(),
}));
return students;
} catch (error) {
console.error('Error retrieving students:', error);
throw error;
}
}
10.2 Understanding the Result
collection(db, 'students') references the collection.
getDocs() retrieves the matching documents.
querySnapshot.docs contains the returned document snapshots.
map() converts each snapshot into an object suitable for application use.
- The
id field makes it easier to identify each record in a React Native list.
10.3 Displaying the Retrieved Data
The returned data can be displayed using React Native components such as FlatList, View, and Text.
import { FlatList, Text, View } from 'react-native';
type Student = {
id: string;
name?: string;
course?: string;
};
type StudentListProps = {
students: Student[];
};
export function StudentList({ students }: StudentListProps) {
return (
item.id}
renderItem={({ item }) => (
{item.name ?? 'Unnamed Student'}
{item.course ?? 'Course not assigned'}
)}
ListEmptyComponent={No students found.}
/>
);
}
This component receives the student records through props. The parent component is responsible for loading the data and managing the loading and error states.
11. Retrieving Data with Query Filters
Firestore queries allow applications to retrieve documents that meet specific conditions. This can reduce the amount of data the application needs to process.
11.1 Using where()
The where() function filters documents by a field, comparison operator, and comparison value.
import {
collection,
getDocs,
query,
where,
} from 'firebase/firestore';
import db from './services/firestore';
export async function getActiveStudents() {
const studentsQuery = query(
collection(db, 'students'),
where('active', '==', true)
);
const querySnapshot = await getDocs(studentsQuery);
return querySnapshot.docs.map((documentSnapshot) => ({
id: documentSnapshot.id,
...documentSnapshot.data(),
}));
}
This query retrieves documents in which the active field equals true.
11.2 Common Query Operators
| Operator |
Purpose |
Example |
== |
Matches equal values. |
where('active', '==', true) |
!= |
Matches values that are not equal, subject to Firestore query semantics. |
where('status', '!=', 'closed') |
< |
Matches values less than a value. |
where('score', '<', 50) |
<= |
Matches values less than or equal to a value. |
where('age', '<=', 25) |
> |
Matches values greater than a value. |
where('score', '>', 80) |
>= |
Matches values greater than or equal to a value. |
where('age', '>=', 18) |
array-contains |
Matches an array containing a value. |
where('skills', 'array-contains', 'React') |
in |
Matches a value in a supported list of alternatives. |
where('status', 'in', ['open', 'pending']) |
Query operators have specific constraints, and not every combination is supported. Check the official query documentation when building compound queries.
11.3 Filter Students by Course
const studentsQuery = query(
collection(db, 'students'),
where('course', '==', 'React Native')
);
const snapshot = await getDocs(studentsQuery);
11.4 Filter Tasks by Completion Status
const incompleteTasksQuery = query(
collection(db, 'tasks'),
where('completed', '==', false)
);
const snapshot = await getDocs(incompleteTasksQuery);
12. Sorting Data Using orderBy()
The orderBy() function sorts query results by a selected field.
12.1 Sort by Name
import {
collection,
getDocs,
orderBy,
query,
} from 'firebase/firestore';
const studentsQuery = query(
collection(db, 'students'),
orderBy('name', 'asc')
);
const snapshot = await getDocs(studentsQuery);
The asc direction sorts values in ascending order. Use desc for descending order.
12.2 Sort Tasks by Creation Time
const tasksQuery = query(
collection(db, 'tasks'),
orderBy('createdAt', 'desc')
);
const snapshot = await getDocs(tasksQuery);
This query requests tasks in descending order of the createdAt field. Documents without the ordered field are generally excluded from the results, so ensure that the field is present where required.
13. Limiting Results and Pagination
Large collections should not normally be downloaded in full for every screen. Limiting results and retrieving them in pages helps improve performance and control data usage.
13.1 Use limit()
import {
collection,
getDocs,
limit,
orderBy,
query,
} from 'firebase/firestore';
const recentTasksQuery = query(
collection(db, 'tasks'),
orderBy('createdAt', 'desc'),
limit(10)
);
const snapshot = await getDocs(recentTasksQuery);
This query retrieves up to ten matching documents, ordered by creation time.
13.2 Pagination with startAfter()
Pagination retrieves a large result set in smaller groups. Firestore supports cursor-based pagination using functions such as startAfter() with an appropriate ordering field.
import {
collection,
getDocs,
limit,
orderBy,
query,
startAfter,
} from 'firebase/firestore';
const firstPageQuery = query(
collection(db, 'tasks'),
orderBy('createdAt', 'desc'),
limit(10)
);
const firstPage = await getDocs(firstPageQuery);
const lastDocument = firstPage.docs[
firstPage.docs.length - 1
];
if (lastDocument) {
const nextPageQuery = query(
collection(db, 'tasks'),
orderBy('createdAt', 'desc'),
startAfter(lastDocument),
limit(10)
);
const nextPage = await getDocs(nextPageQuery);
}
In a complete implementation, store the last document from each page, handle an empty page, and provide appropriate loading and error states. Choose stable ordering fields and consider adding a secondary ordering field when needed to handle records with equal sort values.
14. Updating Data Using updateDoc()
The updateDoc() function updates selected fields in an existing document. It does not create a missing document.
14.1 Update a Student's Course
import {
doc,
updateDoc,
} from 'firebase/firestore';
import db from './services/firestore';
export async function updateStudentCourse(
studentId: string,
course: string
) {
try {
await updateDoc(
doc(db, 'students', studentId),
{
course,
}
);
console.log('Student course updated.');
} catch (error) {
console.error('Error updating course:', error);
throw error;
}
}
14.2 Update a Task's Completion Status
import {
doc,
updateDoc,
serverTimestamp,
} from 'firebase/firestore';
await updateDoc(doc(db, 'tasks', 'task_001'), {
completed: true,
updatedAt: serverTimestamp(),
});
The update changes the specified fields while preserving other fields in the document.
14.3 Update an Array Field
Firestore supports field-value operations such as arrayUnion() and arrayRemove() for appropriate array updates.
import {
arrayUnion,
doc,
updateDoc,
} from 'firebase/firestore';
await updateDoc(doc(db, 'students', 'student_001'), {
skills: arrayUnion('Firebase'),
});
This example adds the specified value to the array if it is not already present. Use array operations only when an array is an appropriate data model for the application.
15. Deleting Data Using deleteDoc()
The deleteDoc() function removes a document from a collection.
15.1 Example
import {
deleteDoc,
doc,
} from 'firebase/firestore';
import db from './services/firestore';
export async function deleteStudent(studentId: string) {
try {
await deleteDoc(doc(db, 'students', studentId));
console.log('Student document deleted.');
} catch (error) {
console.error('Error deleting student:', error);
throw error;
}
}
15.2 Important Considerations
- Confirm the intended record before performing destructive operations.
- Provide a confirmation dialog when appropriate.
- Handle permission errors and network failures.
- Remember that deleting a document does not automatically delete its subcollections.
- Do not assume that removing a document from the interface also removes it from Firestore.
16. Listening for Real-Time Data with onSnapshot()
Firestore supports real-time listeners. A listener receives an initial snapshot and then receives updates when relevant data changes.
16.1 Listen to a Collection
import {
collection,
onSnapshot,
} from 'firebase/firestore';
import db from './services/firestore';
const unsubscribe = onSnapshot(
collection(db, 'tasks'),
(querySnapshot) => {
const tasks = querySnapshot.docs.map((documentSnapshot) => ({
id: documentSnapshot.id,
...documentSnapshot.data(),
}));
console.log('Current tasks:', tasks);
},
(error) => {
console.error('Task listener error:', error);
}
);
// Call unsubscribe when the listener is no longer needed.
16.2 Listen to a Specific Document
import {
doc,
onSnapshot,
} from 'firebase/firestore';
const unsubscribe = onSnapshot(
doc(db, 'students', 'student_001'),
(documentSnapshot) => {
if (documentSnapshot.exists()) {
console.log('Student:', documentSnapshot.data());
} else {
console.log('Student document does not exist.');
}
},
(error) => {
console.error('Student listener error:', error);
}
);
16.3 Clean Up Listeners in React
When using a real-time listener inside a React component, create it inside an effect and return the unsubscribe function. This prevents unnecessary listeners from remaining active after a screen is removed.
import { useEffect, useState } from 'react';
import {
collection,
onSnapshot,
} from 'firebase/firestore';
import db from '../services/firestore';
export function useTasks() {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const unsubscribe = onSnapshot(
collection(db, 'tasks'),
(snapshot) => {
setTasks(
snapshot.docs.map((item) => ({
id: item.id,
...item.data(),
}))
);
setLoading(false);
setError(null);
},
(listenerError) => {
setError(listenerError.message);
setLoading(false);
}
);
return unsubscribe;
}, []);
return { tasks, loading, error };
}
This example uses any[] for brevity. In a production TypeScript project, define an explicit task type and validate data when appropriate.
16.4 One-Time Reads vs Real-Time Listeners
| Feature |
getDoc()/getDocs() |
onSnapshot() |
| Behavior |
Retrieves a snapshot of data for a read operation. |
Subscribes to ongoing updates. |
| Best for |
Details screens or data that does not need continuous updates. |
Live lists, collaborative features, and changing status displays. |
| Cleanup |
No persistent listener to unsubscribe from. |
Unsubscribe when the listener is no longer needed. |
| Usage consideration |
Refresh or request again when updated data is needed. |
Monitor listener lifetime and usage. |
17. Integrating Firestore with a React Native Screen
A React Native screen typically loads data, stores it in component state, and renders a loading indicator, error message, empty state, or list of results.
17.1 Example Task List Screen
import { useEffect, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Text,
View,
} from 'react-native';
import {
collection,
getDocs,
} from 'firebase/firestore';
import db from '../services/firestore';
type Task = {
id: string;
title?: string;
completed?: boolean;
};
export default function TaskListScreen() {
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
let active = true;
async function loadTasks() {
try {
const snapshot = await getDocs(
collection(db, 'tasks')
);
const results = snapshot.docs.map((item) => ({
id: item.id,
...item.data(),
})) as Task[];
if (active) {
setTasks(results);
setError('');
}
} catch (requestError) {
if (active) {
setError('Unable to load tasks. Please try again.');
}
} finally {
if (active) {
setLoading(false);
}
}
}
loadTasks();
return () => {
active = false;
};
}, []);
if (loading) {
return ;
}
if (error) {
return {error};
}
return (
item.id}
ListEmptyComponent={No tasks available.}
renderItem={({ item }) => (
{item.title ?? 'Untitled Task'}
{item.completed ? 'Completed' : 'Pending'}
)}
/>
);
}
17.2 Explanation
useState() stores the tasks, loading status, and error message.
useEffect() loads the data when the component mounts.
getDocs() retrieves documents from Firestore.
FlatList efficiently renders a list of records.
keyExtractor uses each document ID as the list key.
- The cleanup flag prevents state updates after the component has unmounted.
18. Handling Loading, Empty, and Error States
Database operations are asynchronous. A good mobile interface should clearly communicate the current state of a request.
18.1 Loading State
Show a progress indicator while waiting for data. This helps users understand that the application is working rather than frozen.
18.2 Empty State
If a query returns no documents, display a useful message such as “No tasks available yet.” Where appropriate, provide an action for creating the first record.
18.3 Error State
If a request fails, show a clear message and offer a retry action where suitable. During development, log diagnostic information without exposing sensitive details to users.
18.4 Example Error Handling
async function loadStudents() {
try {
const students = await getAllStudents();
return students;
} catch (error) {
console.error('Unable to retrieve students:', error);
throw new Error('Unable to load student records.');
}
}
19. Firestore Offline Data and Synchronization
Firestore client SDKs provide offline capabilities in supported environments. The exact persistence behavior and configuration differ by SDK and platform, so verify the requirements for your React Native setup.
19.1 What Is Offline Access?
Offline access allows an application to work with eligible cached data when the device temporarily loses connectivity. Supported local operations may be synchronized with the backend after connectivity returns.
19.2 Important Considerations
- Not every requested document will necessarily be available in the local cache.
- Offline query results depend on the data available locally.
- Applications should distinguish locally available information from confirmed server state where necessary.
- Concurrent updates may require careful conflict-handling decisions.
- Do not assume that every React Native SDK setup has the same persistence behavior.
20. Firestore Security Rules
Security Rules control which requests can read or write Firestore data. They are essential for protecting private records and enforcing access policies.
20.1 Authentication and Authorization
Authentication identifies a signed-in user. Authorization determines what that user is allowed to do. A user being signed in does not automatically mean they should have access to every document.
20.2 Example: Restricting Access to User-Owned Tasks
The following example illustrates a Firestore Security Rules pattern for task documents that contain an ownerId field. Review and adapt it to your full data model before deployment.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /tasks/{taskId} {
allow create: if request.auth != null
&& request.resource.data.ownerId
== request.auth.uid;
allow read, delete: if request.auth != null
&& resource.data.ownerId
== request.auth.uid;
allow update: if request.auth != null
&& resource.data.ownerId
== request.auth.uid
&& request.resource.data.ownerId
== resource.data.ownerId;
}
}
}
This rule is an illustrative starting point, not a complete production policy. It does not validate every field, restrict all possible updates, or define every business requirement. Add appropriate field validation and test the rules against unauthorized and malformed requests.
20.3 Security Best Practices
- Do not deploy unrestricted read and write rules in production.
- Use Firebase Authentication where user identity is required.
- Validate ownership and roles in Security Rules.
- Validate expected field types and permitted values.
- Test both permitted and denied operations.
- Do not rely on hidden screens or client-side validation as security controls.
- Keep administrative credentials outside the mobile application.
Official reference: Firebase Security Rules Documentation.
21. Common Firestore Errors and Troubleshooting
| Problem |
Possible Cause |
Recommended Solution |
| Permission denied |
Security Rules reject the operation. |
Check the authenticated user, document ownership, and rules. |
| Document does not exist |
The document ID is incorrect or the document was deleted. |
Check the collection path and use exists() before accessing data. |
| Collection returns no data |
The collection is empty or query filters exclude the records. |
Inspect the stored documents and verify query conditions. |
| Invalid query |
The query uses unsupported constraints or incompatible field types. |
Review query operators, data types, and Firestore query requirements. |
| Index required |
A query requires an index that is not yet available. |
Follow the error's index creation instructions and review the query design. |
| Data appears out of order |
The sort field is missing, inconsistent, or incorrectly typed. |
Check field presence, type consistency, and ordering. |
| Duplicate records |
The application creates new documents repeatedly. |
Check event handlers and decide whether a generated or stable document ID is appropriate. |
| Listener keeps running |
The unsubscribe function is not called. |
Clean up listeners when the screen or component no longer needs them. |
| Slow list screen |
The query retrieves too many records or causes unnecessary reads. |
Use filters, limits, pagination, and suitable query patterns. |
| Write operation fails |
Network, configuration, validation, or permission issue. |
Inspect the error and test the operation with valid data and permissions. |
22. Best Practices for Storing and Retrieving Data
- Use clear collection and field names.
- Keep document structures consistent across records.
- Use auto-generated IDs for records that do not need a stable identifier.
- Use custom IDs when the application has a safe and well-defined reason for doing so.
- Use server timestamps for server-based creation or update times.
- Retrieve only the records needed for the current screen.
- Use filters, sorting, limits, and pagination for larger data sets.
- Use listeners only when continuous updates are required.
- Unsubscribe from listeners during component cleanup.
- Handle loading, empty, and error states in the user interface.
- Design Security Rules before storing real user data.
- Validate data and access permissions rather than trusting the client.
- Monitor read/write usage and review pricing.
- Test database operations and rules in a development environment.
23. Practical Project: Student Management Application
Build a student management application using React Native and Cloud Firestore. This project combines document creation, retrieval, filtering, updating, and deletion.
23.1 Project Requirements
- Add student records with name, email, course, and active status.
- Display the saved students in a list.
- Open an individual student's details.
- Filter students by course or active status.
- Update student information.
- Delete a student record after confirmation.
- Display loading indicators and helpful error messages.
- Protect the data using appropriate Firestore Security Rules.
23.2 Suggested Firestore Structure
students (collection)
|
|-- student_document_id (document)
|-- name: "Aarav Sharma"
|-- email: "[email protected]"
|-- course: "React Native"
|-- active: true
|-- createdAt: server timestamp
|-- updatedAt: server timestamp
23.3 Suggested Screens
| Screen |
Purpose |
Firestore Operation |
| Student List |
Display student records. |
getDocs() or onSnapshot() |
| Add Student |
Create a new record. |
addDoc() |
| Student Details |
Display a selected student's information. |
getDoc() |
| Edit Student |
Change existing information. |
updateDoc() |
| Delete Student |
Remove an existing record. |
deleteDoc() |
23.4 Development Steps
- Create a React Native or Expo project.
- Create a Firebase project and configure the JavaScript SDK or native SDK approach.
- Create the Firestore database.
- Initialize Firestore in a reusable service file.
- Build a form to capture student information.
- Implement the create operation using
addDoc().
- Retrieve records using
getDocs().
- Display the data using
FlatList.
- Add filtering and sorting functionality.
- Implement update and delete operations.
- Handle loading states, validation, and errors.
- Configure and test Security Rules.
24. Practice Exercises
Exercise 1: Create a Collection
Create a collection named products and add at least five documents with product name, category, price, and availability fields.
Exercise 2: Retrieve a Single Product
Use getDoc() to retrieve one product using its document ID. Handle the case where the document does not exist.
Exercise 3: Filter Products
Write a query to retrieve products from a particular category and another query to retrieve only available products.
Exercise 4: Sort and Limit Results
Retrieve the ten most recently added records using an appropriate timestamp field, sorting direction, and result limit.
Exercise 5: Update a Document
Build an edit form that updates selected fields without replacing unrelated fields.
Exercise 6: Add a Real-Time Listener
Use onSnapshot() to update a task list when a task changes. Ensure that the listener is unsubscribed when no longer required.
Exercise 7: Secure the Collection
Design Security Rules that restrict users to the records they are authorized to access. Test both allowed and denied operations.
25. Interview Questions and Answers
Q1. What is Cloud Firestore?
Cloud Firestore is a NoSQL cloud database that stores application information in documents organized into collections.
Q2. What is the difference between a collection and a document?
A collection groups documents. A document is an individual record containing fields and values.
Q3. What is a document ID?
A document ID identifies a document within its collection and is used to construct the document's path.
Q4. What is the difference between addDoc() and setDoc()?
addDoc() creates a document with a generated ID, while setDoc() writes to a specified document path.
Q5. What is the difference between getDoc() and getDocs()?
getDoc() retrieves one specified document, while getDocs() retrieves documents from a collection or query.
Q6. What is the purpose of where()?
where() adds a filter condition to a query so the application can retrieve matching documents.
Q7. What does orderBy() do?
orderBy() sorts query results using a selected field in ascending or descending order.
Q8. What is the purpose of limit()?
limit() restricts the number of documents returned by a query.
Q9. What is pagination?
Pagination retrieves a large result set in smaller groups, often using cursor functions such as startAfter().
Q10. What is the difference between updateDoc() and setDoc()?
updateDoc() modifies selected fields of an existing document. setDoc() writes data to a specified document and can replace existing fields unless merge behavior is requested.
Q11. What is onSnapshot()?
onSnapshot() registers a listener that receives an initial snapshot and subsequent updates for a document or query.
Q12. Why should Firestore listeners be unsubscribed?
Unsubscribing prevents unnecessary listeners from remaining active after the application no longer needs their updates.
Q13. What is serverTimestamp()?
serverTimestamp() requests that Firestore use a server-generated timestamp for a field.
Q14. What causes a permission-denied error?
A request may be rejected because the applicable Security Rules do not allow the operation for the current identity and resource.
Q15. Why should applications use pagination?
Pagination helps manage large result sets, reduces unnecessary data retrieval, and can improve list-screen performance.
Q16. What are Firestore Security Rules?
They define which requests are allowed to read or modify Firestore data based on conditions such as authentication, ownership, and data validation.
Q17. Does deleting a document automatically delete its subcollections?
No. Deleting a document does not automatically delete its subcollections. Additional cleanup may be necessary.
Q18. What is the difference between a one-time read and a real-time listener?
A one-time read retrieves data for a particular operation. A real-time listener remains active to receive relevant changes until it is unsubscribed or otherwise terminated.
26. Quick Revision Checklist
- Firestore is a NoSQL document database.
- Collections contain documents.
- Documents contain fields and values.
addDoc() creates a document with an automatically generated ID.
setDoc() writes data to a specified document path.
getDoc() retrieves a single document.
getDocs() retrieves documents from a collection or query.
where() filters query results.
orderBy() sorts results.
limit() restricts the result count.
startAfter() can be used for cursor-based pagination.
updateDoc() changes selected fields of an existing document.
deleteDoc() removes a document.
onSnapshot() listens for real-time changes.
- Use appropriate loading, empty, and error states.
- Protect records using Firestore Security Rules.
- Use efficient queries and clean up listeners.
27. Official Learning Resources
28. Conclusion
Cloud Firestore provides a flexible way to store and retrieve application data in React Native applications. By understanding collections, documents, and fields, developers can design a clear data structure and implement the CRUD operations required by their applications.
Functions such as addDoc(), setDoc(), getDoc(), getDocs(), updateDoc(), and deleteDoc() support common data-management tasks. Query filters, sorting, limits, and pagination help retrieve appropriate records efficiently, while onSnapshot() supports real-time updates.
Reliable Firestore applications also require secure access rules, appropriate error handling, listener cleanup, and efficient data retrieval. Practicing these techniques in a student management or task management project provides a strong foundation for building more advanced Firebase-powered mobile applications.
Explore the course: React Native Training at JustAcademy
Register for a demo: JustAcademy Course Demo Registration
Visit: JustAcademy Official Website